package gpsservice;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Hashtable;
import java.util.StringTokenizer;
import gnu.io.*;
import org.knopflerfish.service.log.LogRef;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.Date;

public class GPSHandler extends Thread 
{
	private static final String CURRENT_LAT_LONG = "currentLatLong";
	private static final String LATITUDE = "latitude";
	private static final String LONGITUDE = "longitude";
	private DBConnectionPool dbConnectionPool;
	private boolean running = true;
	private BufferedReader bufReader = null;
	private EventAdmin eventAdmin;
	private long lastTimePolled = 0L;
	private long pollTimeInterval = 0L;
	private CommPortIdentifier portId;
	private GPSConfig config;
    private InputStream inputStream;
    private SerialPort serialPort;
    private boolean systemTimeSynced = false;
	private LogRef log;
	private double latitude = 999d;
	private double longitude = 999d;
	private String currLatLongQuery = "SELECT id FROM curr_lat_long";
	private String currLatLongInsert = "INSERT INTO  curr_lat_long VALUES(1, ?, ?, default)";
	private String currLatLongUpdate = "UPDATE curr_lat_long SET lat=?, lng=?, logtime=?";
	
	public GPSHandler(EventAdmin eventAdmin) throws Exception
	{
		this.eventAdmin = eventAdmin;
		log = new LogRef(Activator.bc);
		lastTimePolled = System.currentTimeMillis();
		try 
		{
			config = GPSConfig.getConfig();
			dbConnectionPool = DBConnectionPool.getInstance(config.getJdbcConnectionURI());
			openSerialPort();
			pollTimeInterval = config.getGpsPollTimeInterval()*1000;
		} 
		catch (Exception e)
		{
			e.printStackTrace();
			throw e;
		}
		Connection conn = null;
		PreparedStatement preparedStmt1 = null;
		PreparedStatement preparedStmt2 = null;
		ResultSet rs1 = null;
		try 
    	{
			conn = dbConnectionPool.getConnection();
			conn.setAutoCommit(false);
			preparedStmt1 = dbConnectionPool.getPreparedStmt(conn, currLatLongQuery);
			rs1= preparedStmt1.executeQuery();
	    	if (rs1.next() == false)
	    	{
	    		preparedStmt2 = dbConnectionPool.getPreparedStmt(conn, currLatLongInsert);
	    		preparedStmt2.setDouble(1, 999);
	    		preparedStmt2.setDouble(2, 999);
	    		preparedStmt2.executeUpdate();
		    	preparedStmt2.close();
		    	preparedStmt2 = null;
	    	}
	    	rs1.close();
	    	rs1 = null;
	    	preparedStmt1.close();
	    	preparedStmt1 = null;
	    	conn.commit();
    	}
    	catch (Exception e) 
		{
			log.error("Error while starting GPSHandler thread");
    		e.printStackTrace();
    		throw e;
		}
		finally
		{
			if (rs1 != null)
    		{
    			try {rs1.close();} catch (SQLException e) {} 
    			rs1 = null;
    		}
    		if (preparedStmt1 != null)
    		{
    			try {preparedStmt1.close();} catch (SQLException e) {} 
    			preparedStmt1 = null;
    		}
    		if (preparedStmt2 != null)
    		{
    			try {preparedStmt2.close();} catch (SQLException e) {} 
    			preparedStmt2 = null;
    		}
			if (conn != null)
			{
				try {conn.close();} catch (SQLException e) {}
				conn = null;
			}
		}
	}
	
	public void run() 
	{
		int maxConsecutiveInvalidReads = 0;
		while (running == true)
		{				
			if (serialPort == null || inputStream == null || bufReader == null)
			{
				closeSerialPort();
				try {Thread.sleep(5000);} catch (InterruptedException e1){}
				openSerialPort();
			}
			else
			{
				String nmeaSentence = null;
				try 
				{
					if ((System.currentTimeMillis() - lastTimePolled) >= pollTimeInterval)
					{
						long lastTimePosted = System.currentTimeMillis();
						while (inputStream.available() > 0)
						{
							nmeaSentence = bufReader.readLine();
							if (nmeaSentence != null && nmeaSentence.startsWith("$GPGGA"))
							{
								maxConsecutiveInvalidReads = 0;
								StringTokenizer st = new StringTokenizer(nmeaSentence, ",");
								int i = 0;
								String latVal=null, latDir=null, longVal=null, longDir=null;
								int fixQuality=0, satelliteCount=0;
								while(st.hasMoreTokens())
								{
									String tok = st.nextToken();
									switch(i)
									{
										case 2:
											latVal = tok;
											break;
										case 3:
											latDir = tok;
											break;
										case 4:
											longVal = tok;
											break;
										case 5:
											longDir = tok;
											break;
										case 6:
											fixQuality = Integer.parseInt(tok);
											break;
										case 7:
											satelliteCount = Integer.parseInt(tok);
											break;
										default:
											break;
									}
									i++;
								}
								if (latVal== null || latDir==null ||
									longVal==null || longDir==null ||
									latVal.equals("") || latDir.equals("") ||
									longVal.equals("") ||  longDir.equals("") || fixQuality < 0 ||
									fixQuality > config.getValidFixQuality() || satelliteCount < 3)
								{
									
									log.debug("Invalid $GPGGA - latVal: " +latVal + "latDir: "+latDir + "longVal: "+
											longVal + " longDir: " +longDir + " fixQuality: " + fixQuality + " satelliteCount: " +satelliteCount);
									log.debug("nmeaSentence: " +nmeaSentence);
									latitude = 999d;
									longitude= 999d;
									continue;
								}
								else
								{
									double latDeg = Double.parseDouble(latVal.substring(0,2));
									double latMin = Double.parseDouble(latVal.substring(2));
									latitude = latDeg + (latMin/60d);
									double longDeg = Double.parseDouble(longVal.substring(0,3));
									double longMin = Double.parseDouble(longVal.substring(3));
									longitude = longDeg + (longMin/60d);
									if (latDir.equals("S"))
										latitude = -1 * latitude;
									if (longDir.equals("W"))
										longitude =  -1 * longitude;
									if (System.currentTimeMillis() - lastTimePosted > pollTimeInterval)
									{
										notifyLatLong();
										lastTimePosted = System.currentTimeMillis();
									}
								}
							}
							else if (nmeaSentence != null && nmeaSentence.startsWith("$GPRMC") && systemTimeSynced == false)
							{
								StringTokenizer st = new StringTokenizer(nmeaSentence, ",");
								int i = 0;
								String timeToken = null;
								String satelliteFixtoken = null;
								String dateToken = null;
								while(st.hasMoreTokens())
								{
									String tok = st.nextToken();
									switch(i)
									{
										case 1:
											timeToken = tok;
											break;
										case 2:
											satelliteFixtoken = tok;
											break;
										case 9:
											dateToken = tok;
											break;
										default:
											break;
									}
									i++;
								}
								if (satelliteFixtoken != null && satelliteFixtoken.equals("A") && timeToken !=null && dateToken != null)
								{
									DateFormat gmtFormat = new SimpleDateFormat("ddMMyy hhmmss");
									TimeZone gmtTime = TimeZone.getTimeZone("GMT");
									gmtFormat.setTimeZone(gmtTime);
									Date parsedUtilDate = gmtFormat.parse(dateToken.trim() + " " + timeToken.trim());
									DateFormat istFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
									TimeZone istTime = TimeZone.getTimeZone("IST");
									istFormat.setTimeZone(istTime);
									String syncTime = istFormat.format(parsedUtilDate);
									StringTokenizer st1 = new StringTokenizer(syncTime, " ");
									Process process = Runtime.getRuntime().exec("/sbin/setdate.sh" +
										                                    " "  +  st1.nextToken() + //EEE
										                                    " "  +  st1.nextToken() + //MMM
										                                    " "  +  st1.nextToken() + //dd
										                                    " "  +  st1.nextToken() + //HH:mm:ss
										                                    " "  +  st1.nextToken() + //zzz
										                                    " "  +  st1.nextToken()); //yyyy
									process.waitFor();
									process.destroy();
									log.debug("System time synchronized to satellite time");
									systemTimeSynced = true;
								}
							}
							else
							{
								maxConsecutiveInvalidReads++;
							}
							if (maxConsecutiveInvalidReads > 25)
							{
								closeSerialPort();
								openSerialPort();
								maxConsecutiveInvalidReads = 0;
								break;
							}
						}
						lastTimePolled = System.currentTimeMillis();
						if (latitude != 999d && longitude != 999d)
						{
							notifyLatLong();
							latitude = 999d;
							longitude = 999d;
						}
					}
					Thread.sleep(1000);
				}
				catch(NumberFormatException nfe)
				{
					log.debug("Invalid GPS input : "+nmeaSentence + " "+ nfe);
					latitude = 999d;
					longitude= 999d;
					try {Thread.sleep(500);} catch (InterruptedException e1){}
				}
				catch (Exception e) 
				{
					log.error("Exception occured while reading serial port  "+ e);
					latitude = 999d;
					longitude= 999d;
					e.printStackTrace();
					closeSerialPort();
					try {Thread.sleep(10000);} catch (InterruptedException e1){}
					openSerialPort();
				}
			}
		} 
	}

	private void notifyLatLong()
	{
		double roundedLat = roundTo13Decimals(latitude);
		double roundedLong = roundTo13Decimals(longitude);
		Hashtable currentLatLong = new Hashtable();
		currentLatLong.put(LATITUDE, String.valueOf(roundedLat));
		currentLatLong.put(LONGITUDE,  String.valueOf(roundedLong));
		Event event = new Event(CURRENT_LAT_LONG, currentLatLong);
		this.eventAdmin.postEvent(event);
		log.debug("gpsservice: Posted GPS lat long: " + roundedLat + " "+roundedLong);
		Connection conn = null;
		PreparedStatement preparedStmt1 = null;
		try 
    	{
			conn = dbConnectionPool.getConnection();
    		preparedStmt1 = dbConnectionPool.getPreparedStmt(conn, currLatLongUpdate);
    		preparedStmt1.setDouble(1, roundedLat);
    		preparedStmt1.setDouble(2, roundedLong);
    		Timestamp timestamp = new Timestamp(System.currentTimeMillis());
    		preparedStmt1.setTimestamp(3, timestamp);
    		preparedStmt1.executeUpdate();
	    	preparedStmt1.close();
	    	preparedStmt1 = null;
    	}
    	catch (Exception e) 
		{
			log.error("GPSHandler: Error in notifying lat long");
    		e.printStackTrace();
		}
		finally
		{
    		if (preparedStmt1 != null)
    		{
    			try {preparedStmt1.close();} catch (SQLException e) {} 
    			preparedStmt1 = null;
    		}
			if (conn != null)
			{
				try {conn.close();} catch (SQLException e) {}
				conn = null;
			}
		}
	}
	private void openSerialPort()
	{
		try
		{

			portId = CommPortIdentifier.getPortIdentifier (config.getSerialPortName());
	        serialPort = (SerialPort) portId.open("GPS Service", 2000);
	        serialPort.notifyOnDataAvailable(true);
	        serialPort.setSerialPortParams(config.getSerialBaud(), 
	        							   SerialPort.DATABITS_8,
	        							   SerialPort.STOPBITS_1,
	        							   SerialPort.PARITY_NONE);
	        serialPort.setDTR(false);
	        serialPort.setRTS(false);
			if (serialPort != null)
			{
				inputStream = serialPort.getInputStream();
				bufReader = new BufferedReader(new InputStreamReader(inputStream));
				if (bufReader.ready())
					bufReader.readLine();
			}
			else
			{
				throw new Exception();
			}
			log.debug("Serial Port Opened");
		}
		catch (Exception e) 
		{
			log.error("Exception occured while opening serial port  "+ e);
			e.printStackTrace();
			closeSerialPort();
			try {Thread.sleep(20000);} catch (InterruptedException e1){}
		}
	}
	
	private void closeSerialPort()
	{
		try 
		{
			portId = null;
			if (bufReader != null)
			{
				bufReader.close();
				bufReader = null;
			}
			if (inputStream != null)
			{
				inputStream.close();
				inputStream = null;
			}
			if (serialPort != null)
			{
				serialPort.close();
				serialPort = null;
			}
			log.debug("Serial Port Closed");
		} 
		catch (Exception e) 
		{
			log.error("Error while closing serial port: " +e) ;
		} 
	}
	
    private double roundTo13Decimals(double input)
    {
        BigDecimal bd = new BigDecimal(input);
        BigDecimal bdRound = bd.setScale(13, BigDecimal.ROUND_HALF_UP );
        return bdRound.doubleValue();
    }
    
	public void stopThread() 
	{
		this.running = false;
		closeSerialPort();
		try {Thread.sleep(10000);} catch (InterruptedException e) {}
		closeSerialPort();
		try {Thread.sleep(10000);} catch (InterruptedException e) {}
	}
}
